package com.paziy.jan.calc.expressions;

import com.paziy.jan.calc.literals.Computable;
import com.paziy.jan.calc.literals.Literal;
import com.paziy.jan.calc.literals.UnknownLiteral;
import com.paziy.jan.calc.literals.brackets.Bracket;
import com.paziy.jan.calc.literals.brackets.BracketLeft;
import com.paziy.jan.calc.literals.brackets.BracketRight;
import com.paziy.jan.calc.literals.brackets.exeptions.CanNotCreateBracket;
import com.paziy.jan.calc.literals.functions.Function;
import com.paziy.jan.calc.literals.numbers.*;
import com.paziy.jan.calc.literals.operations.*;
import com.paziy.jan.calc.literals.operations.associations.*;
import com.paziy.jan.calc.literals.operations.exeptions.CanNotCreateOperation;

import java.math.BigDecimal;
import java.util.*;
import java.util.regex.*;


public class Expression {

    private Collection<Literal> collection;

    private static final Comparator<Computable> comparator = new Computable.PriorityComparator();

    public Expression() {
        collection = new ArrayList<Literal>();
    }

    public Expression(String expression) throws CanNotCreateOperation, CanNotCreateBracket, UnknownLiteral {
        this();
        parsString(expression);
    }

    public Expression(Collection<Literal> collection) {
        this.collection = collection;
    }

    public Collection<Literal> getCollection() {
        return collection;
    }

    //todo false return?
    public boolean add(String expression) throws CanNotCreateOperation, CanNotCreateBracket, UnknownLiteral {
        parsString(expression);
        return true;
    }

    protected void parsString(String s) throws UnknownLiteral, CanNotCreateOperation, CanNotCreateBracket {

        int position = 0;
        int length = s.length();

        Pattern spacesPattern = Pattern.compile("\\s+");
        Matcher spacesMatcher = spacesPattern.matcher(s);

        //todo Big Decimal value need tests
        Pattern number = Pattern.compile("\\d+(\\.\\d+)?");
        Matcher nMatcher = number.matcher(s);

        mainRound:
        while (position < length) {
            //пустое место
            if(spacesMatcher.find(position) && spacesMatcher.start() == position) {
                position = spacesMatcher.end();

                continue;
            }

            //числа
            if(nMatcher.find(position) && nMatcher.start() == position) {
                String numberText = nMatcher.group();

                collection.add(new Num(new BigDecimal(numberText)));

                position = nMatcher.end();

                continue;// mainRound;
            }

            //скобки
            for (Pattern bracketPattern : Bracket.getBrackets().keySet()) {
                Matcher bracketMatcher = bracketPattern.matcher(s);

                if (bracketMatcher.find(position) && bracketMatcher.start() == position) {

                    Class<? extends Bracket> operationClass = Bracket.getBrackets().get(bracketPattern);

                    try {
                        Bracket bracket = operationClass.newInstance();
                        collection.add(bracket);
                    }
                    catch (Exception e) {
                        throw new CanNotCreateBracket(e);
                    }

                    position = bracketMatcher.end();

                    continue mainRound;
                }
            }

            //операции и функции
            for (Pattern computablePattern : Computable.getComputables().keySet()) {
                Matcher computableMatcher = computablePattern.matcher(s);

                if (computableMatcher.find(position) && computableMatcher.start() == position) {

                    Class<? extends Computable> operationClass = Computable.getComputables().get(computablePattern);

                    try {
                        Computable operation = operationClass.newInstance();
                        collection.add(operation);
                    }
                    catch (Exception e) {
                        throw new CanNotCreateOperation(e);
                    }

                    position = computableMatcher.end();

                    continue mainRound;
                }
            }


            Pattern errorPattern = Pattern.compile("[\\w\\.,]+");
            Matcher errorMatcher = errorPattern.matcher(s);

            int end;

            if (errorMatcher.find(position)) {
                end = errorMatcher.end();
            }
            else {
                end = s.length() - 1;
            }

            String errorString = s.substring(position, errorMatcher.end());

            throw new UnknownLiteral(errorString, position, end);
        }
    }

    public ReversePolishNotation makeRPN() {

        Collection<Literal> rpn = new ArrayList<Literal>(collection.size());

        LinkedList<Literal> stack = new LinkedList<Literal>();

        for (Literal el : collection) {

            //число
            if (el instanceof Num) {
                rpn.add(el);
                continue;
            }

            //функция
            if (el instanceof Function) {
                stack.push(el);
                continue;
            }

            //левая скобка
            if (el instanceof BracketLeft) {
                stack.push(el);
                continue;
            }

            //правая скобка
            if (el instanceof BracketRight) {
                while (!(stack.peek() instanceof BracketLeft)) {
                    rpn.add(stack.pop());
                }

                stack.pop(); //выталкиваем левую скобку

                //Выталкиваем функцию
                if (stack.peek() instanceof Function) {
                    rpn.add(stack.pop());
                }
                continue;
            }

            //оператор
            if (el instanceof Operation) {
                Operation operation = (Operation) el;

                while (!stack.isEmpty() && stack.peek() instanceof Operation) {

                    Operation stOperation = (Operation) stack.peek();

                    boolean right = operation.getAssociation() instanceof AssociatedRight
                            && comparator.compare(operation, stOperation) < 0;

                    boolean left = (operation.getAssociation() instanceof AssociatedLeft ||
                            operation.getAssociation() instanceof AssociatedFull)
                            && comparator.compare(operation, stOperation) <= 0;

                    if (right || left) {
                        rpn.add(stack.pop());
                    } else {
                        break;
                    }
                }

                stack.push(el);

                continue;
            }

            //todo exception unknown object in expression
        }

        while (!stack.isEmpty()) {
            rpn.add(stack.pop());
        }

        return new ReversePolishNotation(rpn);
    }

}
